/*
* Portuguese eID Card API .NET Wrapper
*
* Written by Fernando Nunes (fernando.nunes@devscope.net)
* Copyright (c) 2009 DevScope Information Systems
*
* This library is free software; you can redistribute it and/or 
* modify it under the terms of the GNU Lesser General Public 
* License as published by the Free Software Foundation; either 
* version 2.1 of the License, or (at your option) any later version. 
* 
* This library is distributed in the hope that it will be useful, 
* but WITHOUT ANY WARRANTY; without even the implied warranty of 
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
* Lesser General Public License for more details. 
* 
* You should have received a copy of the GNU Lesser General Public 
* License along with this library; if not, write to the Free Software 
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 
*/ 

#include <msclr\lock.h>
#include <windows.h>
#include <winscard.h>
#pragma comment(lib, "winscard.lib")

#include "stdafx.h"
#include "SCWatcher.h"

using namespace System;
using namespace System::Threading;
using namespace DevScope::CartaoDeCidadao;


SCWatcher::SCWatcher()
{
	// Private constructor
	this->StartDaemon();
}

// Implements singleton pattern
SCWatcher^ SCWatcher::GetInstance()
{
	if (!Instance)
	{
		msclr::lock l(instanceLock);
		if (!Instance)
			Instance = gcnew SCWatcher();
	}
	return Instance;
}

bool SCWatcher::Idle::get()
{
	msclr::lock l(idleLock);
	return this->idled;
}

bool SCWatcher::Stopped::get()
{
	msclr::lock l(stopLock);
	return this->stopped;
}

void SCWatcher::Start()
{
	msclr::lock l(stopLock);
	if (this->stopped)
	{
		this->stopped = false;
		this->StartDaemon();
	}
}

void SCWatcher::StartDaemon()
{
	ThreadStart^ pThread = gcnew ThreadStart(this, &SCWatcher::DoWork);
  this->workerThread = gcnew Thread(pThread);
  this->workerThread->Start();
}

void SCWatcher::Stop()
{
	msclr::lock l(stopLock);
	this->stopped = true;
}

DWORD SCWatcher::CountCardsInList(LPTSTR cardList)
{
	DWORD i = 0;
	LPTSTR pReader = cardList;
	for(; i < MAXIMUM_SMARTCARD_READERS; ++i)
	{
		if (pReader == NULL || *pReader == '\0')
			break;
		pReader += wcslen((wchar_t *)pReader) + 1;
	}
	return i;
}

String ^SCWatcher::GetATRString(BYTE* atrBytes, DWORD atrLen)
{
	array<Byte>^ managedName = ArrayUnmanagedToManaged<Byte, BYTE>(atrBytes, atrLen);
	String^ cardATR  = gcnew String("");
	for(int i = 0; i < managedName->Length; ++i)
	{
		cardATR += String::Format("{0:X2}", managedName[i]);
		if (i + 1 < managedName->Length)
			cardATR += " ";
	}
	return cardATR;
}

SCARD_READERSTATE* SCWatcher::GetReaderChanges(SCARDCONTEXT hSC, SCARD_READERSTATE* readerList, LPDWORD listCount, LPDWORD cch)
{
	LPTSTR pReader = NULL;
	DWORD newReadersCount = 0;
	LPTSTR newCardReaders = NULL;
	SCARD_READERSTATE* newReaderList;

	*cch = 0;
	DWORD lResult = SCardListReaders(hSC, NULL, NULL, cch);
	if (lResult != SCARD_S_SUCCESS && lResult != SCARD_E_NO_READERS_AVAILABLE)
		this->OnError(lResult, GetSCError(lResult)); // In case of errors notify parent
	// The method return makes the daemon sleep(idle) while card discovery isn't sucessfull or until parent stops it
	if (*cch != 0)
		newCardReaders = new TCHAR[*cch];
	lResult = SCardListReaders(hSC, NULL, newCardReaders, cch);
	if (lResult != SCARD_S_SUCCESS && lResult != SCARD_E_NO_READERS_AVAILABLE)
		this->OnError(lResult, GetSCError(lResult));
	
	// Count and set list of Currently Connected Card Readers
	newReadersCount = this->CountCardsInList(newCardReaders);
	newReaderList = new SCARD_READERSTATE[newReadersCount];
	pReader = newCardReaders;
	DWORD i;
	for(i = 0; i < newReadersCount; ++i)
	{
		if (pReader == NULL || *pReader == '\0')
			break;
		int strSize = wcslen(pReader);
		newReaderList[i].pvUserData = NULL;
		newReaderList[i].szReader = new TCHAR[strSize];
		wcscpy_s((wchar_t *)newReaderList[i].szReader, strSize+1, pReader);
		newReaderList[i].dwCurrentState = SCARD_STATE_UNAWARE;
		pReader += wcslen((wchar_t *)pReader) + 1;
	}

	bool readersChanged = false;
	// Check if any reader was removed
	for(i = 0; i < *listCount; ++i)
	{
		bool exists = false;
		DWORD j;
		for(j = 0; j < newReadersCount; ++j)
		{
			if (wcscmp(readerList[i].szReader, newReaderList[j].szReader) == 0)
			{
				exists = true;
				newReaderList[j].dwCurrentState = readerList[i].dwCurrentState;
			}
		}
		if (!exists)
		{
			readersChanged = !exists;
			this->ReaderRemoved(Interop::WStrToNetStr((wchar_t*)readerList[i].szReader));
		}
	}
	// Check if any reader was inserted
	for(i = 0; i < newReadersCount; ++i)
	{
		bool exists = false;
		DWORD j;
		for(j = 0; j < *listCount; ++j)
		{
			if (wcscmp(newReaderList[i].szReader, readerList[j].szReader) == 0)
				exists = true;
		}
		if (!exists)
		{
			readersChanged = !exists;
			this->ReaderInserted(Interop::WStrToNetStr((wchar_t*)newReaderList[i].szReader));
		}
	}

	*listCount = newReadersCount;
	delete[] readerList;
	return newReaderList;
}

// Monitor for insertions and removals of Readers and Cards
void SCWatcher::DoWork()
{
	SCARDCONTEXT hSC;
	LONG lReturn = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hSC);
	if (lReturn != SCARD_S_SUCCESS)
	{
		this->OnError(lReturn, this->GetSCError(lReturn)); // Notify parent and stop
		this->Stop();
		return;
	}
	
	DWORD listCount = 0;
	SCARD_READERSTATE* readerList = new SCARD_READERSTATE[0];
	DWORD cch = 0;//SCARD_AUTOALLOCATE; this only brings trouble :(

	// Get list of CardReaders
	while(true)
	{
		readerList = GetReaderChanges(hSC, readerList, &listCount, &cch);
		DWORD cchOld = cch;
		if (listCount == 0)
		{
			do
			{
				Thread::Sleep(3000);
				// Setting idled only after Thread Sleep, because Object lock only frees when it goes out of scope
				msclr::lock l(idleLock);
				this->idled = true;
				if (this->Stopped)
					break;
			}
			while ((SCardListReaders(hSC, NULL, NULL, &cch) == SCARD_E_NO_READERS_AVAILABLE) && cch == cchOld);
			readerList = GetReaderChanges(hSC, readerList, &listCount, &cch);
		}

		// Check the CardReaders State
		while((lReturn = SCardGetStatusChange(hSC, 3000, readerList, listCount)) == SCARD_E_TIMEOUT || lReturn == SCARD_S_SUCCESS)
		{
			if (this->Stopped)
				break;

			// Check if new CardReader was inserted or removed
			if (((lReturn  = SCardListReaders(hSC, NULL, NULL, &cch)) == SCARD_S_SUCCESS && cch != cchOld) || lReturn == SCARD_E_NO_READERS_AVAILABLE) 
				break;

			DWORD i;
			for (i = 0; i < listCount; i++)
			{
				// Update the current card state
				if ((readerList[i].dwCurrentState & SCARD_STATE_PRESENT) != 0 && (readerList[i].dwEventState & SCARD_STATE_EMPTY) != 0)
				{
					// Fire REMOVAL Event
					this->CardRemoved(Interop::WStrToNetStr((wchar_t*)readerList[i].szReader));
					readerList[i].dwCurrentState = readerList[i].dwEventState;
				}
				else if ((readerList[i].dwCurrentState & SCARD_STATE_EMPTY) != 0 && (readerList[i].dwEventState & SCARD_STATE_PRESENT) != 0)
				{
					// Fire INSERTION Event
					this->CardInserted(Interop::WStrToNetStr((wchar_t*)readerList[i].szReader), GetATRString(readerList[i].rgbAtr, readerList[i].cbAtr));
					readerList[i].dwCurrentState = readerList[i].dwEventState;
				}
				else
				{
					if (readerList[i].dwCurrentState == 0)
						readerList[i].dwCurrentState = SCARD_STATE_EMPTY;
					else
						readerList[i].dwCurrentState = readerList[i].dwEventState;
				}
			} // End CardReader iteration
		} // End While we Wait for CardReader Changes

		// Check for errors/stop or continue loop and update card list
		if (lReturn != SCARD_S_SUCCESS && lReturn != SCARD_E_TIMEOUT && lReturn != SCARD_E_NO_READERS_AVAILABLE)
		{
			this->OnError(lReturn, this->GetSCError(lReturn));
			this->Stop();
			break; // Break out of the CardReader List iteration loop
		}

		if (this->Stopped)
			break;
	} // End CardReader Discovery Loop

	// Clean up
	delete [] readerList;
	SCardReleaseContext(hSC);
}

array<String^>^ SCWatcher::ListReaders()
{
	System::Collections::Generic::List<String^> cardList = gcnew System::Collections::Generic::List<String^>();

	SCARDCONTEXT hSC;
	LONG lReturn = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hSC);
	if (lReturn != SCARD_S_SUCCESS)
	{
		throw gcnew Exception(GetSCError(lReturn));
	}

	LPTSTR  pmszReaders = NULL;
	LPTSTR  pReader;
	DWORD   cch = SCARD_AUTOALLOCATE;

	lReturn = SCardListReaders(hSC, NULL, (LPTSTR)&pmszReaders, &cch);
	switch(lReturn)
	{
		case SCARD_E_NO_READERS_AVAILABLE:
		case SCARD_S_SUCCESS:
			{
				pReader = pmszReaders;
				while (pReader != NULL && *pReader != '\0')
				{
					cardList.Add(Interop::WStrToNetStr(pReader));
					pReader += lstrlen(pReader) + 1;
				}
				SCardFreeMemory(hSC, pmszReaders );
			}
			break;
		default:
				// TODO: Take appropriate action.
				break;
	}

	SCardReleaseContext(hSC);
	if (lReturn != SCARD_S_SUCCESS && lReturn != SCARD_E_NO_READERS_AVAILABLE)
		throw gcnew Exception(GetSCError(lReturn));

	return cardList.ToArray();
}

String^ SCWatcher::GetSCError(long errorCode)
{
	String^ errorMessage = "";
	switch(errorCode)
	{
		case ERROR_BROKEN_PIPE:
			errorMessage = "The client attempted a smart card operation in a remote session, such as a client session running on a terminal server, and the operating system in use does not support smart card redirection.";
			break;
		case SCARD_E_BAD_SEEK:
			errorMessage = "An error occurred in setting the smart card file object pointer.";
			break;
		case SCARD_E_CANCELLED:
			errorMessage = "The action was canceled by an SCardCancel request.";
			break;
		case SCARD_E_CANT_DISPOSE:
			errorMessage = "The system could not dispose of the media in the requested manner.";
			break;
		case SCARD_E_CARD_UNSUPPORTED:
			errorMessage = "The smart card does not meet minimal requirements for support.";
			break;
		case SCARD_E_CERTIFICATE_UNAVAILABLE:
			errorMessage = "The requested certificate could not be obtained.";
			break;
		case SCARD_E_COMM_DATA_LOST:
			errorMessage = "A communications error with the smart card has been detected.";
			break;
		case SCARD_E_DIR_NOT_FOUND:
			errorMessage = "The specified directory does not exist in the smart card.";
			break;
		case SCARD_E_DUPLICATE_READER:
			errorMessage = "The reader driver did not produce a unique reader name.";
			break;
		case SCARD_E_FILE_NOT_FOUND:
			errorMessage = "The specified file does not exist in the smart card.";
			break;
		case SCARD_E_ICC_CREATEORDER:
			errorMessage = "The requested order of object creation is not supported.";
			break;
		case SCARD_E_ICC_INSTALLATION:
			errorMessage = "No primary provider can be found for the smart card.";
			break;
		case SCARD_E_INSUFFICIENT_BUFFER:
			errorMessage = "The data buffer for returned data is too small for the returned data.";
			break;
		case SCARD_E_INVALID_ATR:
			errorMessage = "An ATR string obtained from the registry is not a valid ATR string.";
			break;
		case SCARD_E_INVALID_CHV:
			errorMessage = "The supplied PIN is incorrect.";
			break;
		case SCARD_E_INVALID_HANDLE:
			errorMessage = "The supplied handle was not valid.";
			break;
		case SCARD_E_INVALID_PARAMETER:
			errorMessage = "One or more of the supplied parameters could not be properly interpreted.";
			break;
		case SCARD_E_INVALID_TARGET:
			errorMessage = "Registry startup information is missing or not valid.";
			break;
		case SCARD_E_INVALID_VALUE:
			errorMessage = "One or more of the supplied parameter values could not be properly interpreted.";
			break;
		case SCARD_E_NO_ACCESS:
			errorMessage = "Access is denied to the file.";
			break;
		case SCARD_E_NO_DIR:
			errorMessage = "The supplied path does not represent a smart card directory.";
			break;
		case SCARD_E_NO_FILE:
			errorMessage = "The supplied path does not represent a smart card file.";
			break;
		case SCARD_E_NO_MEMORY:
			errorMessage = "Not enough memory available to complete this command.";
			break;
		case SCARD_E_NO_READERS_AVAILABLE:
			errorMessage = "No smart card reader is available.";
			break;
		case SCARD_E_NO_SERVICE:
			errorMessage = "The smart card resource manager is not running.";
			break;
		case SCARD_E_NO_SMARTCARD:
			errorMessage = "The operation requires a smart card, but no smart card is currently in the device.";
			break;
		case SCARD_E_NO_SUCH_CERTIFICATE:
			errorMessage = "The requested certificate does not exist.";
			break;
		case SCARD_E_NOT_READY:
			errorMessage = "The reader or card is not ready to accept commands.";
			break;
		case SCARD_E_NOT_TRANSACTED:
			errorMessage = "An attempt was made to end a nonexistent transaction.";
			break;
		case SCARD_E_PCI_TOO_SMALL:
			errorMessage = "The PCI receive buffer was too small.";
			break;
		case SCARD_E_PROTO_MISMATCH:
			errorMessage = "The requested protocols are incompatible with the protocol currently in use with the card.";
			break;
		case SCARD_E_READER_UNAVAILABLE:
			errorMessage = "The specified reader is not currently available for use.";
			break;
		case SCARD_E_READER_UNSUPPORTED:
			errorMessage = "The reader driver does not meet minimal requirements for support.";
			break;
		case SCARD_E_SERVICE_STOPPED:
			errorMessage = "The smart card resource manager has shut down.";
			break;
		case SCARD_E_SHARING_VIOLATION:
			errorMessage = "The smart card cannot be accessed because of other outstanding connections.";
			break;
		case SCARD_E_SYSTEM_CANCELLED:
			errorMessage = "The action was canceled by the system, presumably to log off or shut down.";
			break;
		case SCARD_E_TIMEOUT:
			errorMessage = "The user-specified time-out value has expired.";
			break;
		case SCARD_E_UNEXPECTED:
			errorMessage = "An unexpected card error has occurred.";
			break;
		case SCARD_E_UNKNOWN_CARD:
			errorMessage = "The specified smart card name is not recognized.";
			break;
		case SCARD_E_UNKNOWN_READER:
			errorMessage = "The specified reader name is not recognized.";
			break;
		case SCARD_E_UNKNOWN_RES_MNG:
			errorMessage = "An unrecognized error code was returned from a layered component.";
			break;
		case SCARD_E_UNSUPPORTED_FEATURE:
			errorMessage = "This smart card does not support the requested feature.";
			break;
		case SCARD_E_WRITE_TOO_MANY:
			errorMessage = "An attempt was made to write more data than would fit in the target object.";
			break;
		case SCARD_F_COMM_ERROR:
			errorMessage = "An internal communications error has been detected.";
			break;
		case SCARD_F_INTERNAL_ERROR:
			errorMessage = "An internal consistency check failed.";
			break;
		case SCARD_F_UNKNOWN_ERROR:
			errorMessage = "An internal error has been detected, but the source is unknown.";
			break;
		case SCARD_F_WAITED_TOO_LONG:
			errorMessage = "An internal consistency timer has expired.";
			break;
		case SCARD_P_SHUTDOWN:
			errorMessage = "The operation has been aborted to allow the server application to exit.";
			break;
		case SCARD_S_SUCCESS:
			errorMessage = "No error was encountered.";
			break;
		case SCARD_W_CANCELLED_BY_USER:
			errorMessage = "The action was canceled by the user.";
			break;
		case SCARD_W_CHV_BLOCKED:
			errorMessage = "The card cannot be accessed because the maximum number of PIN entry attempts has been reached.";
			break;
		case SCARD_W_EOF:
			errorMessage = "The end of the smart card file has been reached.";
			break;
		case SCARD_W_REMOVED_CARD:
			errorMessage = "The smart card has been removed, so further communication is not possible.";
			break;
		case SCARD_W_RESET_CARD:
			errorMessage = "The smart card was reset.";
			break;
		case SCARD_W_SECURITY_VIOLATION:
			errorMessage = "Access was denied because of a security violation.";
			break;
		case SCARD_W_UNPOWERED_CARD:
			errorMessage = "Power has been removed from the smart card, so that further communication is not possible.";
			break;
		case SCARD_W_UNRESPONSIVE_CARD:
			errorMessage = "The smart card is not responding to a reset.";
			break;
		case SCARD_W_UNSUPPORTED_CARD:
			errorMessage = "The reader cannot communicate with the card, due to ATR string configuration conflicts.";
			break;
		case SCARD_W_WRONG_CHV:
			errorMessage = "The card cannot be accessed because the wrong PIN was presented.";
			break;
		default:
			errorMessage = "Unknown error occurred communicating with the SmartCard Reader.";
			break;
	}
	return errorMessage;
}